#pragma once
// OpenGL and GLEW Header Files and Libraries
#include <GL/glew.h>
#include <GL/wglew.h>
#include <GL/glfw.h>


#include "Utility.h"

#include "OBJ.h"
#include "Camera.h"

float fps =0;
float currentTime, pastTime; 

/*
Rendering Elements
*/
std::vector<glm::vec3> aLightsPos;
std::vector<glm::vec3> aLightsCol;
std::vector<float> aLightsIntensivity;

//std::hash_map<std::string, std::vector<glm::vec3>> allTangents, allBitangents, allVerticesPerMaterial, allNormalsPerMaterial;
//std::hash_map<std::string, std::vector<glm::vec2>> allUVsPerMaterial;

std::vector<glm::vec3> ves; // used for boxing

/* 
Rendering Elements
*/

GLuint VertexArrayID;
GLuint BoundinGBoxesID;

std::hash_map<std::string, Material> Materials = std::hash_map<std::string, Material>(); 

std::hash_map<std::string, Mesh> Model;
std::hash_map<std::string, Material> materials;

ProgramHandles programs;
ShaderUniforms uniforms;
SkyboxUniforms skyboxUniforms;
Camera* cam;

Scene scene;

std::hash_map<int, Action> actions;

int currentStep = -1;

int trianglesDisplayed;

//GLuint texture;
GLuint vertexbuffer[255], uvbuffer[255], normalbuffer[255], tangentbuffer[255], bitangentbuffer[255];


void computeTangentBasis(
	std::vector<glm::vec3> & vertices,
	std::vector<glm::vec2> & uvs,
	std::vector<glm::vec3> & normals,
	//output
	std::vector<glm::vec3> & tangents,
	std::vector<glm::vec3> & bitangents)
{
	for(int i = 0; i<vertices.size(); i+=3)
	{

		// Shortcuts for vertices
		glm::vec3 & v0 = vertices[i+0];
		glm::vec3 & v1 = vertices[i+1];
		glm::vec3 & v2 = vertices[i+2];
 
		// Shortcuts for UVs
		glm::vec2 & uv0 = uvs[i+0];
		glm::vec2 & uv1 = uvs[i+1];
		glm::vec2 & uv2 = uvs[i+2];
 
		// Edges of the triangle : postion delta
		glm::vec3 deltaPos1 = v1-v0;
		glm::vec3 deltaPos2 = v2-v0;
 
		// UV delta
		glm::vec2 deltaUV1 = uv1-uv0;
		glm::vec2 deltaUV2 = uv2-uv0;

		float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
		glm::vec3 tangent = (deltaPos1 * deltaUV2.y   - deltaPos2 * deltaUV1.y)*r;
		glm::vec3 bitangent = (deltaPos2 * deltaUV1.x   - deltaPos1 * deltaUV2.x)*r;

		tangents.push_back(tangent);
		tangents.push_back(tangent);
		tangents.push_back(tangent);
		
		bitangents.push_back(bitangent);
		bitangents.push_back(bitangent);
		bitangents.push_back(bitangent);

	}

}

void resize(int w, int h)
{
	
	uScrWidth = w;
	uScrHeight = h;
    glViewport(0,0,(GLsizei)w, (GLsizei)h);
    gluPerspective(fov, (GLfloat)w/ (GLfloat)h , nearPlane, farPlane);
 	
}

void initShaders()
{
	programs.Main = LoadProgram("data/shaders/lukes.vert", "data/shaders/lukes.frag");
	programs.SkyBox = LoadProgram("data/shaders/skybox.vert", "data/shaders/skybox.frag");
	

	uniforms.MVP = glGetUniformLocation(programs.Main, "MVP");
	uniforms.V = glGetUniformLocation(programs.Main, "V");
	uniforms.M = glGetUniformLocation(programs.Main, "M");
	uniforms.P = glGetUniformLocation(programs.Main, "P");
	uniforms.NoLights = glGetUniformLocation(programs.Main, "NoLights");
	uniforms.MV3x3 = glGetUniformLocation(programs.Main, "MV3x3");

	uniforms.AmbientColor = glGetUniformLocation(programs.Main, "AmbientColor");
	uniforms.DifuseColor = glGetUniformLocation(programs.Main, "DifuseColor");
	uniforms.SpecularColor = glGetUniformLocation(programs.Main, "SpecularColor");
	
	uniforms.DiffuseTextureSampler = glGetUniformLocation(programs.Main, "DiffuseTextureSampler");
	uniforms.AmbientTextureSampler = glGetUniformLocation(programs.Main, "AmbientTextureSampler");
	uniforms.NormalTextureSampler = glGetUniformLocation(programs.Main, "NormalTextureSampler");
	
	uniforms.LightPosition = glGetUniformLocation(programs.Main, "LightPosition");
	uniforms.LightColor = glGetUniformLocation(programs.Main, "LightColor");
	uniforms.LightPower = glGetUniformLocation(programs.Main, "LightPower");
	

	//setting uniforms for skybox
	skyboxUniforms.MVP = glGetUniformLocation(programs.SkyBox, "MVP");


}

static int addLight(lua_State *lua)
{

	glm::vec3 pos;
	pos.x = lua_tonumber(lua,1); 
	pos.y = lua_tonumber(lua,2); 
	pos.z = lua_tonumber(lua,3);

	glm::vec3 color;
	color.x = lua_tonumber(lua,4); 
	color.y = lua_tonumber(lua,5); 
	color.z = lua_tonumber(lua,6);

	float intensivity = lua_tonumber(lua,7);
	int id = lua_tonumber(lua, 8);
	id--;

	scene.lights[id].lightsPos = pos;
	scene.lights[id].lightsCol = color;
	scene.lights[id].lightsIntensivity = intensivity;
	scene.lights[id].alive = true;
	
	std::cout << "light added! \n";

	return 0;
}


static int addModel(lua_State *lua)
{
	Mesh temp;

	size_t x = 128;
	std::string name = lua_tostring(lua, 1, &x);

	//check if model is in our list already
	std::hash_map<std::string, Mesh>::iterator iter = Model.find(name);
    if(iter != Model.end()) {
		return 0;
	}else {
		std::cout << "adding " << name.c_str() << " to resources \n"; 
	}

	std::string path = "data/models/";
	path.append(lua_tostring(lua, 2, &x));

	if(loadOBJ(path.c_str(), temp.vertices, temp.uvs, temp.normals))
	{
		computeTangentBasis(temp.vertices, temp.uvs, temp.normals, temp.tangents, temp.bitangents);
		std::cout << path.c_str() << " Loaded! \n";

	}else {

		std::cout << "Problem with Loading The model\n"; 
	}

	

	Model[name] = temp;

	return 0;
	
}

static int addEntity(lua_State *lua)
{
	Entity tmp; 
	int id = lua_tonumber(lua, 1);
	id--;
	//check if model is in our list already
	std::hash_map<int, Entity>::iterator iter = scene.entities.find(id);
    if(iter != scene.entities.end()) {
		return 0;
	}else {
		std::cout << "adding entity id: " << id << " to scene \n"; 
	}

	size_t x = 128;

	tmp.modelUsed= lua_tostring(lua, 2, &x);

	tmp.positionStart = glm::vec3(lua_tonumber(lua, 3), lua_tonumber(lua, 4), lua_tonumber(lua, 5));
	tmp.positionEnd = glm::vec3(lua_tonumber(lua, 6), lua_tonumber(lua, 7), lua_tonumber(lua, 8));
	tmp.rotationStart = glm::vec3(lua_tonumber(lua, 9), lua_tonumber(lua, 10), lua_tonumber(lua, 11));
	tmp.rotationEnd = glm::vec3(lua_tonumber(lua, 12), lua_tonumber(lua, 13), lua_tonumber(lua, 14));
	tmp.scale = glm::vec3(lua_tonumber(lua, 15), lua_tonumber(lua, 16), lua_tonumber(lua, 17));
	tmp.finished_animation = false;
	tmp.model = glm::mat4(1);
	tmp.mesh = Model[tmp.modelUsed];

	scene.entities[id] = tmp;

	std::cout << "done! \n";
	return 0;
}

static int addMaterial(lua_State *lua)
{
	Material tmp;
	
	size_t x = 128;
	std::string name = lua_tostring(lua, 1, &x);
	std::hash_map<std::string, Material>::iterator iter = materials.find(name);
	if(iter != materials.end()) {
		return 0;
	}else {
		std::cout << "adding new material: " << name << "to memo \n";
	}

	std::string path = "data/textures/";
	std::string d = "";
	d.append(path);
	std::string t = lua_tostring(lua, 2, &x);
	d.append(t);
	if(tmp.DifuseTexture = loadTGA_glfw(d.c_str()))
	{
		int x = 9;
	}else {
		int x = 10;
	}
	
	tmp.AmbientTexture = tmp.DifuseTexture;
	tmp.SpecularTexture =tmp.DifuseTexture;

	d = "";
	d.append(path);
	d.append(lua_tostring(lua, 3, &x));
	tmp.NormalTexture = loadTGA_glfw(d.c_str());
	
	tmp.AmbiantColor = glm::vec3(lua_tonumber(lua, 4), lua_tonumber(lua, 5), lua_tonumber(lua, 6));
	tmp.DifuseColor = glm::vec3(lua_tonumber(lua, 7), lua_tonumber(lua, 8), lua_tonumber(lua, 9));
	tmp.Shinies = 1;
	tmp.name = name;
	materials[name] = tmp;

	std::cout << "done! \n";
	return 0;

}

static int addAction(lua_State *lua)
{
	Action tmp;

	size_t x = 128;
	int id = lua_tonumber(lua, 1);
	id--;

	std::hash_map<int, Action>::iterator iter = actions.find(id);
	if(iter != actions.end()) {
		return 0;
	}else {
		std::cout << "adding new step: " << id << "\n";
	}

	std::string s_act = lua_tostring(lua,2, &x);
	whatToDo act;
	if(s_act == "NEW" || s_act == "new")
	{
		act = whatToDo::NEW;

	}else if(s_act == "ROTATE" || s_act == "rotate")
	{
		act = whatToDo::ROTATE;
	}else if(s_act == "MOVE" || s_act == "move")
	{
		act = whatToDo::MOVE;
	}else if(s_act == "REMOVE" || s_act == "remove")
	{
		act = whatToDo::REMOVE;
	}else if(s_act == "CLEAR" || s_act == "clear")
	{
		act = whatToDo::CLEAR;
	}else {
		act = whatToDo::DUM;
	}
	tmp.action = act;

	tmp.entityID = lua_tonumber(lua, 3);
	std::string t_axis = lua_tostring(lua, 4, &x);
	if(t_axis == "X" || t_axis == "x")
	{
		tmp.axis = glm::vec3(1,0,0);
	}else if(t_axis == "Y" || t_axis == "y")
	{
		tmp.axis = glm::vec3(0,1,0);
	}else if(t_axis == "Z" || t_axis == "z")
	{
		tmp.axis = glm::vec3(0,0,1);
	}else if(t_axis == "-X" || t_axis == "-x"){
		tmp.axis = glm::vec3(-1,0,0);
	
	}else if(t_axis == "-Y" || t_axis == "-y"){
		tmp.axis = glm::vec3(0,-1,0);
	}else if(t_axis == "-Z" || t_axis == "-z"){
		tmp.axis = glm::vec3(0,0,-1);
	}


	tmp.value = lua_tonumber(lua,5);
		
	
	actions[id].axis = tmp.axis;
	actions[id].action = tmp.action;
	actions[id].complete = false;
	actions[id].entityID = tmp.entityID;
	actions[id].value = tmp.value;

	return 0;
}

bool initLUA(const char* name)
{
	int iErr = false;
	lua_State *lua = lua_open();
	luaL_openlibs(lua);

	if((iErr = luaL_loadfile (lua, name)) == 0)
	{
		
		lua_pushcclosure(lua, addLight, 0);
		lua_setglobal (lua, "addLight");

		lua_pushcclosure(lua, addMaterial, 0);
		lua_setglobal(lua, "addMaterial");

		lua_pushcclosure(lua, addModel, 0);
		lua_setglobal (lua, "addObject");

		lua_pushcclosure(lua, addEntity, 0);
		lua_setglobal (lua, "addEntity");

		lua_pushcclosure(lua, addAction, 0);
		lua_setglobal (lua, "addAction");

		//Call main
		if((iErr = lua_pcall(lua, 0, LUA_MULTRET, 0)) == 0)
		{
			//Push the function name onto the stack
			lua_pushstring(lua, "initWorld");

			lua_gettable (lua, LUA_GLOBALSINDEX);
			lua_pcall(lua, 0,0,0);

		}else{
			std::cout << ":( \n";
			return false;
		}
	}else {
		std::cout << "Y U no workn?!\n";
		return false;
	}
	lua_close(lua);
	return true;
}

glm::vec3 scale(1);

void update(float dt)
{
	int xpos, ypos;

	glfwGetMousePos(&xpos, &ypos);
	cam->update();

}

bool Leg = false;
void render2DText()
{
	char text[256];
	sprintf(text,"FPS: %.1f", fps);
	printText2D(text, 10, 10, 36);
	
	if(Leg == false){
		sprintf(text,"'L' Show Info ");
		printText2D(text, 10, 1700, 36);
	}else {
		sprintf(text,"'L' Hide Info ");
		printText2D(text, 10, 1700, 36);
		sprintf(text,"'SPACE' Pause ");
		printText2D(text, 10, 1650, 36);
		sprintf(text,"'R' Restart   ");
		printText2D(text, 10, 1600, 36);
		sprintf(text,"'A,S,D,W/arrows' Move");
		printText2D(text, 10, 1550, 36);
		sprintf(text,"'Mouse' Rotation");
		printText2D(text, 10, 1500, 36);
		sprintf(text,"Author: Lukasz Iwanski 07007698");
		printText2D(text, 10, 1450, 36);

	}
	//sprintf(text,"Triangles: %d", trianglesDisplayed);
	//printText2D(text, 10, 70, 24);
	//sprintf(text,"Renderer: %s", glGetString(GL_RENDERER));
	//printText2D(text, 10, 2*uScrHeight - 24, 24);

	//sprintf(text,"Cam Dir X: %f Y: %f Z: %f", cam->direction.x, cam->direction.y, cam->direction.z);
	//printText2D(text, 600, 2*uScrHeight - 24, 24);

}

void createBuffers()
{
	//for each light

	std::hash_map<int, Light>::iterator iterL = scene.lights.begin();
	for(; iterL != scene.lights.end(); ++iterL)
	{
		if(iterL->second.alive == true)
		{
			aLightsPos.push_back(iterL->second.lightsPos);
			aLightsCol.push_back(iterL->second.lightsCol);
			aLightsIntensivity.push_back(iterL->second.lightsIntensivity);
		}
	}

	for(int i= 0; i < scene.entities.size(); i++)
	{
		if(i == 4)
		{	
			scene.entities[i].model = glm::rotate(scene.entities[i].model, (float)90.0f, glm::vec3(1,0,0)); 
			scene.entities[i].model = glm::scale(scene.entities[i].model, glm::vec3(0.15,0.15,0.15));
		}		
	}
	std::cout << "createBuffers finished! \n";
}

void initVAO() {

	glGenVertexArrays(1, &VertexArrayID);
	glBindVertexArray(VertexArrayID);

	createBuffers();
	
	glGenBuffers(scene.entities.size(), &normalbuffer[0]);
	
	glGenBuffers(scene.entities.size(), &vertexbuffer[0]);
	
	glGenBuffers(scene.entities.size(), &uvbuffer[0]);

	glGenBuffers(scene.entities.size(), &tangentbuffer[0]);

	glGenBuffers(scene.entities.size(), &bitangentbuffer[0]);
	
	for(int i= 0; i < scene.entities.size(); i++)
	{
		//we bind vertexes for each model - atm only one material is supported
		glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer[i]);
		//find number of vertices for that model - we assume that there is only one material
		int t = scene.entities[i].mesh.vertices.size();
		glBufferData(GL_ARRAY_BUFFER, t *sizeof(glm::vec3), &scene.entities[i].mesh.vertices[0], GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, uvbuffer[i]);
		glBufferData(GL_ARRAY_BUFFER, t *sizeof(glm::vec2), &scene.entities[i].mesh.uvs[0], GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, normalbuffer[i]);
		//find number of vertices for that model - we assume that there is only one material
		glBufferData(GL_ARRAY_BUFFER, t *sizeof(glm::vec3), &scene.entities[i].mesh.normals[0], GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer[i]);
		//find number of vertices for that model - we assume that there is only one material
		glBufferData(GL_ARRAY_BUFFER, t *sizeof(glm::vec3), &scene.entities[i].mesh.tangents[0], GL_STATIC_DRAW);

		glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer[i]);
		glBufferData(GL_ARRAY_BUFFER, t *sizeof(glm::vec3), &scene.entities[i].mesh.bitangents[0], GL_STATIC_DRAW);


	}
	
	std::cout << "initVAO finished! \n";
	
}

void init()
{
	initDebuger();

	glm::vec3 temp(0);
	cam = new Camera(temp);

	initLUA("data/scripts/general.lua");

	initShaders();
	initVAO();

	glEnable(GL_DEPTH_TEST); // We enable the depth test (also called z buffer)
    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); // Polygon rasterization mode (polygon filled)	
	
	// Initialize our little text library with the Holstein font
	initText2D( "data/fonts/standard.tga" );
	initPouseScreen();
	std::cout << "Init finished! \n";
}

void setSkyBoxUniforms() {
	glUniformMatrix4fv(skyboxUniforms.MVP, 1, GL_FALSE, &cam->mTransform.MVP[0][0]);

}

void setUniforms()
{
	glUniformMatrix4fv(uniforms.MVP, 1, GL_FALSE, &cam->mTransform.MVP[0][0]);
	glUniformMatrix4fv(uniforms.P, 1, GL_FALSE, &cam->mTransform.Projection[0][0]);
	
	glUniformMatrix4fv(uniforms.M, 1, GL_FALSE, &cam->mTransform.Model[0][0]);
	glUniformMatrix4fv(uniforms.V, 1, GL_FALSE, &cam->mTransform.View[0][0]);
	glUniformMatrix3fv(uniforms.MV3x3, 1, GL_FALSE, &cam->mTransform.ModelView3x3Matrix[0][0]);

	int nol = scene.lights.size();
	
	glUniform1i(uniforms.NoLights, nol);
	glUniform3fv(uniforms.LightPosition,sizeof(glm::vec3),&aLightsPos[0][0]);
	glUniform3fv(uniforms.LightColor,sizeof(glm::vec3), &aLightsCol[0][0]);
	glUniform1fv(uniforms.LightPower,nol, &aLightsIntensivity[0]);
	
}


float speed = 2.25;
Action a_current;
Action a_finish;
glm::mat4 animationMatrix(1);
void animate(float dt){
	//animation method - calculate position of geometry.

	if(currentStep > 0 && currentStep >= actions.size())
	{
		//to avoid crash when animations are done.
		//nice idea to add some fancy restart button or something
		int x;
		x = 10;
	}else {
		if(currentStep == -10)
		{
			int z = 10;

		}else {
		
			int entity_id = 0;
		
			if(currentStep < 0)
			{
				a_current = actions[0];
				a_current.complete = false;
				a_current.value = 0;

				a_finish = actions[0];
				currentStep++;

				//firstStep
			}

			entity_id = actions[currentStep].entityID -1;
		
			//check if animation ended and we can move on to next step
			if(actions[currentStep].complete == true)
			{
				//k, move on.
				currentStep++;
				if(currentStep >= actions.size())
				{
					//if next step is above actions size - to avoid problems
					currentStep = -10;
				}else {
					a_current = actions[currentStep];
					a_current.value = 0;
					a_current.complete = false;

					a_finish = actions[currentStep];
				}
			}else
			{
				//nope do the animation!
				if(a_current.action == whatToDo::REMOVE)
				{
					scene.entities[entity_id].alive = false;
					actions[currentStep].complete = true;
				}else 
				if(a_current.action == whatToDo::NEW)
				{
					scene.entities[entity_id].alive = true;
					//reset pos.

					scene.entities[entity_id].model = glm::mat4(1);
					animationMatrix = glm::mat4(1);
					
					//now offset it to suit startpos and start rotation
					scene.entities[entity_id].model = glm::rotate(scene.entities[entity_id].model, scene.entities[entity_id].rotationStart.x, glm::vec3(1,0,0));
					scene.entities[entity_id].model = glm::rotate(scene.entities[entity_id].model, scene.entities[entity_id].rotationStart.y, glm::vec3(0,1,0));
					scene.entities[entity_id].model = glm::rotate(scene.entities[entity_id].model, scene.entities[entity_id].rotationStart.z, glm::vec3(0,0,1));

					scene.entities[entity_id].model = glm::translate(scene.entities[entity_id].model, scene.entities[entity_id].positionStart); //(scene.entities[entity_id].model, scene.entities[entity_id].rotationStart.x, glm::vec3(1,0,0));
					animationMatrix = scene.entities[entity_id].model;

					scene.entities[entity_id].alive = true;
					actions[currentStep].complete = true;
				}else
				if(a_current.action == whatToDo::MOVE || a_current.action == whatToDo::ROTATE)
				{
					animationMatrix = scene.entities[entity_id].model;
					if(a_current.value != a_finish.value)
					{

						//now update model matrix
						if(actions[currentStep].action == whatToDo::MOVE)
						{
							float s = speed /100;
							if(a_finish.value > 0){
								a_current.value += s;
								if(a_current.value > a_finish.value)
									a_current.value = a_finish.value;
							}
							else if(a_finish.value < 0)
							{
								a_current.value -= s;
								if(a_current.value < a_finish.value)
									a_current.value = a_finish.value;
							}
							animationMatrix = glm::translate(animationMatrix, actions[currentStep].axis*(s)); 
						
						}else if(actions[currentStep].action == whatToDo::ROTATE)
						{
							if(a_finish.value > 0){
								a_current.value += speed;
								if(a_current.value > a_finish.value)
									a_current.value = a_finish.value;

							}
							else if(a_finish.value < 0)
							{
								a_current.value -= speed;
								if(a_current.value < a_finish.value)
									a_current.value = a_finish.value;
							}
							animationMatrix = glm::rotate(animationMatrix, speed, actions[currentStep].axis);
							int x = 10;
						}else {
							actions[currentStep].complete = true;
						}
					}else {
						actions[currentStep].complete = true;
					
					}
				}else {
					actions[currentStep].complete = true;
				
				}
			
		
			}
	
			scene.entities[entity_id].model = animationMatrix;

		}
	}
}

void render()
{
	trianglesDisplayed = 0;
	glClearColor(0.3f, 0.5f, 0.9f, 0.0f);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	 glUseProgram(programs.Main);
	for(int i= 0; i < scene.entities.size(); i++)
	{
			if(scene.entities[i].modelUsed != "plane")
			{
				setUniforms();
				glActiveTexture(GL_TEXTURE0);
		
				glBindTexture(GL_TEXTURE_2D, materials["wood"].DifuseTexture);
				glUniform1i(uniforms.DiffuseTextureSampler, 0);
	
				glActiveTexture(GL_TEXTURE1);
				glBindTexture(GL_TEXTURE_2D, materials["wood"].AmbientTexture);
				glUniform1i(uniforms.AmbientTextureSampler, 1);

				glActiveTexture(GL_TEXTURE2);
				glBindTexture(GL_TEXTURE_2D, materials["wood"].NormalTexture);
				glUniform1i(uniforms.NormalTextureSampler, 2);

				glActiveTexture(0);
				glUniform3fv(uniforms.AmbientColor, sizeof(glm::vec3), &materials["wood"].AmbiantColor[0]);
				glUniform3fv(uniforms.DifuseColor, sizeof(glm::vec3), &materials["wood"].AmbiantColor[0]);
				glUniform3fv(uniforms.SpecularColor, sizeof(glm::vec3), &materials["wood"].AmbiantColor[0]);
				glUniform1f(uniforms.Shines, 1);
				glUniformMatrix4fv(uniforms.M, 1, GL_FALSE, &scene.entities[i].model[0][0]);

			}else {
				//floor texture
				scene.entities[i].alive = true; 
				setUniforms();
				glActiveTexture(GL_TEXTURE0);
		
				glBindTexture(GL_TEXTURE_2D, materials["bottom"].DifuseTexture);
				glUniform1i(uniforms.DiffuseTextureSampler, 0);
	
				glActiveTexture(GL_TEXTURE1);
				glBindTexture(GL_TEXTURE_2D, materials["bottom"].AmbientTexture);
				glUniform1i(uniforms.AmbientTextureSampler, 1);

				glActiveTexture(GL_TEXTURE2);
				glBindTexture(GL_TEXTURE_2D, materials["bottom"].NormalTexture);
				glUniform1i(uniforms.NormalTextureSampler, 2);

				glActiveTexture(0);


				glUniform3fv(uniforms.AmbientColor, sizeof(glm::vec3), &materials["bottom"].AmbiantColor[0]);
				glUniform3fv(uniforms.DifuseColor, sizeof(glm::vec3), &materials["bottom"].AmbiantColor[0]);
				glUniform3fv(uniforms.SpecularColor, sizeof(glm::vec3), &materials["bottom"].AmbiantColor[0]);
				glUniform1f(uniforms.Shines, 1);
				glUniformMatrix4fv(uniforms.M, 1, GL_FALSE, &scene.entities[i].model[0][0]);

			}

			if(scene.entities[i].alive == true) 
			{


			// 1rst attribute buffer : vertices
			glEnableVertexAttribArray(0);
			glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer[i]);
			glVertexAttribPointer(
				0,                  // attribute. No particular reason for 0, but must match the layout in the shader.
				3,                  // size
				GL_FLOAT,           // type
				GL_FALSE,           // normalized?
				0,                  // stride
				(void*)0            // array buffer offset
			);
		
			// 2nd attribute buffer : normals
			glEnableVertexAttribArray(1);
			glBindBuffer(GL_ARRAY_BUFFER, uvbuffer[i]);
			glVertexAttribPointer(
			1,                                // attribute. No particular reason for 1, but must match the layout in the shader.
			2,                                // size
			GL_FLOAT,                         // type
			GL_FALSE,                         // normalized?
			0,                                // stride
			(void*)0                          // array buffer offset
			);

			// 2nd attribute buffer : textcord
			glEnableVertexAttribArray(2);
			glBindBuffer(GL_ARRAY_BUFFER, normalbuffer[i]);
			glVertexAttribPointer(
				2,                                // attribute. No particular reason for 1, but must match the layout in the shader.
				3,                                // size
				GL_FLOAT,                         // type
				GL_FALSE,                         // normalized?
				0,                                // stride
				(void*)0                          // array buffer offset
			);

			glEnableVertexAttribArray(3);
			glBindBuffer(GL_ARRAY_BUFFER, tangentbuffer[i]);
			glVertexAttribPointer(
				3,                                // attribute
				3,                                // size
				GL_FLOAT,                         // type
				GL_FALSE,                         // normalized?
				0,                                // stride
				(void*)0                          // array buffer offset
			);

			glEnableVertexAttribArray(4);
			glBindBuffer(GL_ARRAY_BUFFER, bitangentbuffer[i]);
			glVertexAttribPointer(
				4,                                // attribute
				3,                                // size
				GL_FLOAT,                         // type
				GL_FALSE,                         // normalized?
				0,                                // stride
				(void*)0                          // array buffer offset
			);



			
			int s = scene.entities[i].mesh.vertices.size();
			
			glDrawArrays(GL_TRIANGLES, 0, s);

			trianglesDisplayed = trianglesDisplayed+s;
			glDisableVertexAttribArray(0);
			glDisableVertexAttribArray(1);
			glDisableVertexAttribArray(2);
			glDisableVertexAttribArray(3);
			glDisableVertexAttribArray(4);
		}else {
			
			}

	}

	glm::mat4 iden(1);

	if(!pouse)
	{
		renderLightsDebug(scene.lights, cam->mTransform.Projection * cam->mTransform.View * iden);
		render2DText();
	}else {
		renderPouseScreen();
	}
}

void resetAnimation()
{
	currentStep = 0;
	for(int i = 0; i < actions.size(); i++)
	{
		actions[i].complete = false;
	}

	for(int i = 0; i < scene.entities.size(); i++)
	{
		if(i != 4)
		{
			scene.entities[i].model = glm::mat4(1);
			scene.entities[i].alive = false;
			//now offset it to suit startpos and start rotation
			scene.entities[i].model = glm::rotate(scene.entities[i].model, scene.entities[i].rotationStart.x, glm::vec3(1,0,0));
			scene.entities[i].model = glm::rotate(scene.entities[i].model, scene.entities[i].rotationStart.y, glm::vec3(0,1,0));
			scene.entities[i].model = glm::rotate(scene.entities[i].model, scene.entities[i].rotationStart.z, glm::vec3(0,0,1));

			scene.entities[i].model = glm::translate(scene.entities[i].model, scene.entities[i].positionStart); 
		}
	}

	currentStep = -1;
}

void GLFWCALL keyMap(int key, int action)
{
	if (key == GLFW_KEY_SPACE && action == GLFW_PRESS)
		pouse = !pouse;
	if((key == 'R' || key == 'r') && action == GLFW_PRESS)
	{
		resetAnimation();
	}
	if((key == 'L' || key == 'l') && action == GLFW_PRESS)
	{
		Leg = !Leg;
	}
}
int main()
{
	
   int frames = 0;
    currentTime = pastTime = 0;
        
    char   title[150];


    int running = GL_TRUE;

		//init glfw
    if(!glfwInit())
    {
            exit(EXIT_FAILURE);
    }

	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 2); 
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3); // We want OpenGL 3.1
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR,3);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //We don't want the old OpenGL


    if(!glfwOpenWindow(uScrWidth, uScrHeight, 0,0,0,0,0,0, GLFW_WINDOW))
    {
            glfwTerminate();
            exit(EXIT_FAILURE);
    }

    glfwSetWindowTitle("nGine by Lukasz Iwanski");
    glfwSetWindowSizeCallback(resize);
		
    // Check GLEW
    GLenum err = glewInit();
    if(GLEW_OK != err)
    {
            std::cout << "Error: " << glewGetErrorString(err) << std::endl;

    }

    if (!GLEW_VERSION_3_3) { /* for some reason GLEW_VERSION_4_0 is not working */
		std::cout << "Error: OpenGL 3.0 is required" << std::endl;
		exit(1);
	}

    std::cout << "Status: Using GLEW " << glewGetString(GLEW_VERSION) << std::endl;
	if (!GLEW_ARB_vertex_buffer_object)
	{
		std::cout << "VBO not supported\n";
		exit(1);
	}

	glEnable(GL_DEPTH_TEST);
	
	// Accept fragment if it closer to the camera than the former one
	glDepthFunc(GL_LESS); 

	// Cull triangles which normal is not towards the camera
	 glEnable(GL_CULL_FACE);

	init();



    glfwSetKeyCallback(keyMap);
	while(running)
    {
		if(!pouse)
		{
			currentTime = glfwGetTime();
		
		}else {
			currentTime = pastTime;
		
		}
		
		update(currentTime-pastTime);
		if(!pouse) {
			animate(currentTime-pastTime);
		}	
		render();			
		
        glfwSwapBuffers();
		
        running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
                
        if((currentTime-pastTime) > 1.0 || frames == 0)
		{
			fps = (float)frames / (currentTime-pastTime);
			pastTime = currentTime;
			frames = 0;
		}
		frames++;
		//Limit the frameRate to 60fps.
        glfwSwapInterval(1);

    }
		
	// Close OpenGL window and terminate GLFW
	glfwTerminate();

	cleanupText2D();

	glDeleteTextures(1, &materials["wood"].NormalTexture);
	glDeleteTextures(1, &materials["wood"].AmbientTexture);
	glDeleteTextures(1, &materials["wood"].DifuseTexture);

	glDeleteTextures(1, &materials["bottom"].NormalTexture);
	glDeleteTextures(1, &materials["bottom"].AmbientTexture);
	glDeleteTextures(1, &materials["bottom"].DifuseTexture);

	exit(EXIT_SUCCESS);
}
